/*
 * This file Copyright (c) 2015. Walle.
 * (http://www.wallellen.com). All rights reserved.
 *
 *
 * This file is dual-licensed under both the
 * Walle Agreement (WA) and the GNU General Public License.
 * You may elect to use one or the other of these licenses.
 *
 * This file is distributed in the hope that it will be
 * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
 * Redistribution, except as permitted by whichever of the GPL
 * or WA you select, is prohibited.
 *
 * 1. For the GPL license (GPL), you can redistribute and/or
 * modify this file under the terms of the GNU General
 * Public License, Version 3, as published by the Free Software
 * Foundation.  You should have received a copy of the GNU
 * General Public License, Version 3 along with this program;
 * if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * 2. For the Walle Agreement (WA), this file
 * and the accompanying materials are made available under the
 * terms of the WA which accompanies this distribution, and
 * is available at http://www.wallellen.com/agreement.html
 *
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 */

package com.wallellen.server.jetty;


import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;

import java.lang.reflect.Method;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.concurrent.BlockingQueue;

/**
 * Created by walle on 12/16/15.
 */
public class Main {
    private static final String ACCESS_LOG_FILE = "jetty.access.log";
    private static final String PORT = "jetty.port";
    private static final String CONTEXT_PATH = "jetty.context.path";
    private static final String THREADPOOL_MIN = "jetty.threadpool.min";
    private static final String THREADPOOL_MAX = "jetty.threadpool.max";
    private static final String QUEUE_SIZE = "jetty.queue.size";
    private static final int RETAIN_DAYS = 90;

    private Main() {
    }

    public static void main(final String[] args) throws Exception {
        new Main().run(args);
    }

    private void run(final String[] args) throws Exception {
        if (args.length > 0) {
            invokeMain(args);
        } else {
            startJetty();
        }
    }

    private void invokeMain(final String[] args) throws Exception {
        WebAppContext context = createWebContext();
        //context.setParentLoaderPriority(true);
        context.setServer(new Server());
        configClassLoader(context);

        String mainClass = args[0];
        Object[] mainArgs = Arrays.copyOfRange(args, 1, args.length);

        Class<?> cls = context.getClassLoader().loadClass(mainClass);
        Thread.currentThread().setContextClassLoader(context.getClassLoader());
        Method method = cls.getMethod("main", String[].class);
        try {
            method.invoke(null, new Object[]{mainArgs});
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(2);
        }
    }

    private void configClassLoader(final WebAppContext context) throws Exception {
        context.preConfigure();
        context.configure();
    }

    private void startJetty() throws Exception {
        int maxThread = 200; // SUPPRESS
        int minThread = 8; // SUPPRESS
        int idleTime = 60000; // SUPPRESS
        BlockingQueue<Runnable> queue;

        String threadPoolMax = System.getProperty(THREADPOOL_MAX);
        if (threadPoolMax != null) {
            maxThread = Integer.valueOf(threadPoolMax);
        }

        String threadPoolMin = System.getProperty(THREADPOOL_MIN);
        if (threadPoolMin != null) {
            minThread = Integer.valueOf(threadPoolMin);
        }

        String queueSize = System.getProperty(QUEUE_SIZE);
        if (queueSize != null) {
            int size = Integer.valueOf(queueSize);
            queue = new BlockingArrayQueue<>(size, minThread);
        } else {
            queue = new BlockingArrayQueue<>(minThread, minThread);
        }

        QueuedThreadPool threadPool = new QueuedThreadPool(maxThread, minThread, idleTime, queue);
        Server server = new Server(threadPool);
        server.addConnector(createConnector(server));
        server.setHandler(createWebContext());
        server.start();
        server.join();
    }

    private ServerConnector createConnector(final Server server) {
        HttpConfiguration config = new HttpConfiguration();
        config.addCustomizer(new ForwardedRequestCustomizer());
        HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config);

        ServerConnector connector = new ServerConnector(server, connectionFactory);
        connector.setPort(Integer.parseInt(getProperty(PORT)));
        return connector;
    }

    private WebAppContext createWebContext() {
        WebAppContext context = new WebAppContext();
        String contextPath = System.getProperty(CONTEXT_PATH);
        if (contextPath == null) {
            contextPath = "";
        }

        context.setContextPath(contextPath);
        context.setWar(getWarLocation());
        context.setHandler(createLogHandler());
        return context;
    }

    private RequestLogHandler createLogHandler() {
        NCSARequestLog log = new NCSARequestLog(getProperty(ACCESS_LOG_FILE));
        Server server = new Server();
        HandlerCollection handlers = new HandlerCollection();
        ContextHandlerCollection contexts = new ContextHandlerCollection();
        RequestLogHandler requestLogHandler = new RequestLogHandler();
        handlers.setHandlers(new Handler[]{contexts, new DefaultHandler(), requestLogHandler});
        server.setHandler(handlers);

        log.setPreferProxiedForAddress(true);
        log.setRetainDays(RETAIN_DAYS);
        log.setAppend(true);
        log.setExtended(false);
        log.setLogTimeZone("GMT");

        requestLogHandler.setRequestLog(log);

        return requestLogHandler;
    }

    private String getWarLocation() {
        ProtectionDomain domain = Main.class.getProtectionDomain();
        URL location = domain.getCodeSource().getLocation();
        return location.toExternalForm();
    }

    private String getProperty(final String prop) {
        String value = System.getProperty(prop);
        if (value == null) {
            throw new IllegalStateException("Missing required property: " + prop);
        }
        return value;
    }
}
